/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ANIUTIL_COMPOSITE_H
#define ANIUTIL_COMPOSITE_H

#include <ani.h>

#include <string>
#include <vector>

#include "base.h"
#include "primitive.h"

namespace OHOS {
namespace AniUtil {

class UnionAccessor {
public:
    UnionAccessor(ani_env *env, ani_object obj) : env_(env), obj_(obj)
    {
    }

    bool IsInstanceOf(const std::string& cls_name)
    {
        ani_class cls;
        env_->FindClass(cls_name.c_str(), &cls);

        ani_boolean ret;
        env_->Object_InstanceOf(obj_, cls, &ret);
        return ret;
    }

    template<typename T>
    bool IsInstanceOfType();

    template<typename T>
    expected<T, ani_status> Convert();

private:
    ani_env *env_;
    ani_object obj_;
};

template<> inline bool UnionAccessor::IsInstanceOfType<bool>()
{
    return IsInstanceOf("Lstd/core/Boolean;");
}

template<> inline bool UnionAccessor::IsInstanceOfType<int>()
{
    return IsInstanceOf("Lstd/core/Int;");
}

template<> inline bool UnionAccessor::IsInstanceOfType<double>()
{
    return IsInstanceOf("Lstd/core/Double;");
}

template<> inline bool UnionAccessor::IsInstanceOfType<std::string>()
{
    return IsInstanceOf("Lstd/core/String;");
}

template<> inline expected<bool, ani_status> UnionAccessor::Convert<bool>()
{
    if (!IsInstanceOfType<bool>()) {
        return ANI_ERROR;
    }

    ani_boolean aniValue;
    auto ret = env_->Object_CallMethodByName_Boolean(obj_, "unboxed", nullptr, &aniValue);
    if (ret != ANI_OK) {
        return ret;
    }
    return static_cast<bool>(aniValue);
}

template<> inline expected<int, ani_status> UnionAccessor::Convert<int>()
{
    if (!IsInstanceOfType<int>()) {
        return ANI_ERROR;
    }

    ani_int aniValue;
    auto ret = env_->Object_CallMethodByName_Int(obj_, "unboxed", nullptr, &aniValue);
    if (ret != ANI_OK) {
        return ret;
    }
    return static_cast<int>(aniValue);
}

template<> inline expected<double, ani_status> UnionAccessor::Convert<double>()
{
    if (!IsInstanceOfType<double>()) {
        return ANI_ERROR;
    }

    ani_double aniValue;
    auto ret = env_->Object_CallMethodByName_Double(obj_, "unboxed", nullptr, &aniValue);
    if (ret != ANI_OK) {
        return ret;
    }
    return static_cast<double>(aniValue);
}

template<> inline expected<std::string, ani_status> UnionAccessor::Convert<std::string>()
{
    if (!IsInstanceOfType<std::string>()) {
        return ANI_ERROR;
    }

    auto value = ToString()(env_, static_cast<ani_string>(obj_));
    return value;
}


class OptionalAccessor {
public:
    OptionalAccessor(ani_env *env, ani_object obj) : env_(env), obj_(obj)
    {
    }

    bool IsUndefined()
    {
        ani_boolean isUndefined;
        env_->Reference_IsUndefined(obj_, &isUndefined);
        return isUndefined;
    }

    template<typename T>
    expected<T, ani_status> Convert();

private:
    expected<std::string, ani_status> ConvertToString()
    {
        if (IsUndefined()) {
            return ANI_ERROR;
        }

        return ToString()(env_, obj_);
    }

private:
    ani_env *env_;
    ani_object obj_;
};

template<> inline expected<bool, ani_status> OptionalAccessor::Convert<bool>()
{
    if (IsUndefined()) {
        return ANI_ERROR;
    }

    ani_boolean aniValue;
    auto ret = env_->Object_CallMethodByName_Boolean(obj_, "unboxed", nullptr, &aniValue);
    if (ret != ANI_OK) {
        return ret;
    }
    auto value = static_cast<bool>(aniValue);
    return value;
}

template<> inline expected<double, ani_status> OptionalAccessor::Convert<double>()
{
    if (IsUndefined()) {
        return ANI_ERROR;
    }

    ani_double aniValue;
    auto ret = env_->Object_CallMethodByName_Double(obj_, "doubleValue", nullptr, &aniValue);
    if (ret != ANI_OK) {
        return ret;
    }
    auto value = static_cast<double>(aniValue);
    return value;
}

template<> inline expected<std::string, ani_status> OptionalAccessor::Convert<std::string>()
{
    return ConvertToString();
}


class EnumAccessor {
public:
    EnumAccessor(ani_env *env) : env_(env)
    {
    }

    template<typename T>
    expected<ani_enum_item, ani_status> From(ani_enum aniEnum, const T &value)
    {
        return Find(aniEnum, [&](ani_env *env, ani_enum_item &item) {
            ani_int intValue = -1;
            ani_status status = env->EnumItem_GetValue_Int(item, &intValue);
            if (ANI_OK != status) {
                return expected<bool, ani_status>(status);
            }
            if (intValue == static_cast<ani_int>(value)) {
                return expected<bool, ani_status>(true);
            }
            return expected<bool, ani_status>(ANI_ERROR);
        });
    }

    template<typename T>
    expected<T, ani_status> To(ani_enum_item item)
    {
        auto result = ToInt(item);
        if (!result.has_value()) {
            return result.error();
        }
        return static_cast<T>(result.value());
    }

    expected<int32_t, ani_status> ToInt(ani_enum_item item)
    {
        ani_int value;
        ani_status status = env_->EnumItem_GetValue_Int(item, &value);
        if (ANI_OK != status) {
            return status;
        }
        return static_cast<int32_t>(value);
    }

    expected<std::string, ani_status> ToString(ani_enum_item item)
    {
        ani_string strValue;
        ani_status status = env_->EnumItem_GetValue_String(item, &strValue);
        if (ANI_OK != status) {
            return status;
        }
        auto value = OHOS::AniUtil::ToString()(env_, strValue);
        return value;
    }

private:
    template<typename Predicate>
    expected<ani_enum_item, ani_status> Find(ani_enum aniEnum, Predicate pred)
    {
        constexpr int32_t loopMaxNum = 1000;
        for (int32_t index = 0U; index < loopMaxNum; index++) {
            ani_enum_item enumItem{};
            ani_status status = env_->Enum_GetEnumItemByIndex(aniEnum, index, &enumItem);
            if (ANI_OK != status) {
                return status;
            }
            auto result = pred(env_, enumItem);
            if (!result.has_value()) {
                return result.error();
            } else if (result.value()) {
                return enumItem;
            }
        }
        return ANI_ERROR;
    }

private:
    ani_env *env_;
};

} // namespace AniUtil
} // namespace OHOS

#endif
